<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BVH Visualization</title>
</head>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            height: 100vh;
        }

        #container {
            width: 100%;
            height: 50%;
            border: 2px solid #ccc;
            background-color: #fff;
            padding: 10px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        .node circle {
            fill: #999;
            stroke: steelblue;
            stroke-width: 1.5px;
            cursor: pointer;
        }

        .node text {
            font: 12px sans-serif;
        }

        .link {
            fill: none;
            stroke: #555;
            stroke-opacity: 0.4;
            stroke-width: 1.5px;
        }

        .tooltip {
            position: absolute;
            text-align: left;
            width: 300px;
            height: auto;
            padding: 10px;
            font: 12px sans-serif;
            background: lightsteelblue;
            border: 1px solid #333;
            border-radius: 8px;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);
            overflow-y: auto;
            max-height: 400px;
            visibility: hidden;
        }

        .tooltip h3 {
            margin: 0;
            font-size: 14px;
            font-weight: bold;
        }

        .tooltip p {
            margin: 5px 0;
            font-size: 12px;
            word-wrap: break-word;
        }

        .tooltip .indent {
            margin-left: 20px;
        }

        #header {
            margin-bottom: 20px;
            padding: 10px;
            background-color: #e0e0e0;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        #header h2 {
            margin-top: 0;
            color: #333;
        }

        #header p {
            margin: 5px 0;
            font-size: 14px;
            line-height: 1.5;
        }

        #threeContainer {
            width: 75%;
            height: 100%;
        }

        #toggleControls {
            width: 25%;
            height: 100%;
            overflow-y: auto;
            margin-left: 20px;
            border: 2px solid #ccc;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            padding: 10px;
        }

        .toggle-container {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
        }

        .toggle-container label {
            font-size: 14px;
            margin-left: 10px;
        }

        #mainContainer {
            display: flex;
            width: 100%;
            height: 50%;
            justify-content: space-between;
            margin-bottom: 20px;
        }
    </style>
<body>
    <div id="header"></div>
    <div id="container">
        <div class="tooltip"></div>
        <svg id="canvas"></svg>
    </div>
    <br></br>
    <br></br>
    <br></br>
    <div id="mainContainer">
        <div id="threeContainer"></div>
        <div id="toggleControls"></div>
    </div>
    <br></br>
    <br></br>
    <br></br>
    <script src="https://d3js.org/d3.v6.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.130.1/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.130.1/examples/js/controls/OrbitControls.js"></script>
    <script>
        function formatProperties(properties, depth = 0, maxDepth = 2, index = '') {
            if (depth > maxDepth) {
                return '<p><strong>...</strong></p>';
            }

            let html = '';
            for (const [key, value] of Object.entries(properties)) {
                const currentIndex = index ? `${index}.${key}` : key;
                if (typeof value === 'object' && value !== null) {
                    html += `<p><strong>${currentIndex}:</strong><div class="indent">${formatProperties(value, depth + 1, maxDepth, currentIndex)}</div></p>`;
                } else {
                    let displayValue = value;
                    if (typeof value === 'string' && value.length > 100) {
                        displayValue = value.substring(0, 100) + '...';
                    }
                    html += `<p><strong>${currentIndex}:</strong> ${displayValue}</p>`;
                }
            }
            return html;
        }

        d3.json('bvh_dump.json').then(function(treeData) {
            document.getElementById('header').innerHTML = `<h2>Header Information</h2>${formatProperties(treeData.header)}`;

            var width = 1400,
                height = 400; // Adjusted for half the height

            var svg = d3.select("#canvas")
                .attr("width", width)
                .attr("height", height)
                .call(d3.zoom().on("zoom", function (event) {
                    g.attr("transform", event.transform);
                }))
                .append("g");

            var g = svg.append("g");

            var root = d3.hierarchy(treeData.nodes.find(n => n.id === 0), function(d) {
                return treeData.relationships[d.id].map(id => treeData.nodes.find(n => n.id === id));
            });

            var treeLayout = d3.tree().size([height, width - 250]);

            treeLayout(root);

            var link = g.selectAll(".link")
                .data(root.links())
                .enter().append("path")
                .attr("class", "link")
                .attr("d", d3.linkHorizontal()
                    .x(d => d.y)
                    .y(d => d.x));

            var node = g.selectAll(".node")
                .data(root.descendants())
                .enter().append("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${d.y},${d.x})`);

            node.append("circle")
                .attr("r", 10)
                .on("click", function(event, d) {
                    const propertiesHtml = formatProperties(d.data.properties);

                    d3.select(".tooltip")
                        .html(`<h3>ID: ${d.data.id}</h3>${propertiesHtml}`)
                        .style("visibility", "visible")
                        .style("left", (event.pageX + 20) + "px")
                        .style("top", (event.pageY - 20) + "px");
                    d3.selectAll("circle").attr("r", 10);
                    d3.select(this).attr("r", 15);
                });

            node.append("text")
                .attr("dy", 3)
                .attr("x", d => d.children ? -12 : 12)
                .style("text-anchor", d => d.children ? "end" : "start")
                .text(d => d.data.type);

            d3.select("body").on("click", function(event) {
                if (!event.target.closest(".node")) {
                    d3.select(".tooltip").style("visibility", "hidden");
                    d3.selectAll("circle").attr("r", 10);
                }
            });

            initThreeJs(treeData);
        });

        function initThreeJs(treeData) {
            const scene = new THREE.Scene();
            const camera = new THREE.PerspectiveCamera(75, window.innerWidth * 0.7 / 800, 0.1, 1000); // Adjusted for the new layout
            const renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth * 0.7, 800); // Adjusted for half the height
            document.getElementById('threeContainer').appendChild(renderer.domElement);

            // Add orbit controls for zoom and rotate
            const controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.25;
            controls.screenSpacePanning = false;
            controls.maxPolarAngle = Math.PI / 2;

            // Add grid helper and axis helper for reference
            const gridHelper = new THREE.GridHelper(10, 10);
            scene.add(gridHelper);

            const axesHelper = new THREE.AxesHelper(5);
            scene.add(axesHelper);

            const geometries = [];

            function createBox(coord, color, id) {
                const geometry = new THREE.BoxGeometry(
                    coord.x_upper - coord.x_lower,
                    coord.y_upper - coord.y_lower,
                    coord.z_upper - coord.z_lower
                );
                const edges = new THREE.EdgesGeometry(geometry);
                const material = new THREE.LineBasicMaterial({ color: color });
                const box = new THREE.LineSegments(edges, material);
                box.position.set(
                    (coord.x_upper + coord.x_lower) / 2,
                    (coord.y_upper + coord.y_lower) / 2,
                    (coord.z_upper + coord.z_lower) / 2
                );

                scene.add(box);
                geometries.push(box);

                addToggleControl(box, id);
            }

            function createTriangle(vertices, leafColor, id) {
                const geometry = new THREE.BufferGeometry();
                const verticesArray = new Float32Array(vertices.flat());
                geometry.setAttribute('position', new THREE.BufferAttribute(verticesArray, 3));
                const material = new THREE.MeshBasicMaterial({ color: leafColor, side: THREE.DoubleSide, wireframe: true });
                const triangle = new THREE.Mesh(geometry, material);
                scene.add(triangle);
                geometries.push(triangle);

                addToggleControl(triangle, id);
            }

            function createInstance(instanceProperties, color, id) {
                const geometry = new THREE.SphereGeometry(0.1, 32, 32); // Create a sphere geometry
                const material = new THREE.MeshBasicMaterial({ color: color, wireframe: false }); // Create a material with wireframe
                const sphere = new THREE.Mesh(geometry, material); // Create a mesh

                const { obj2world_p } = instanceProperties.part0;
                sphere.position.set(obj2world_p[0], obj2world_p[1], obj2world_p[2]);

                scene.add(sphere);
                geometries.push(sphere);

                addToggleControl(sphere, id);
            }
            function addToggleControl(geometry, id) {
                const toggleContainer = document.createElement('div');
                toggleContainer.className = 'toggle-container';

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = true;
                checkbox.addEventListener('change', () => {
                    geometry.visible = checkbox.checked;
                });

                const labelElement = document.createElement('label');
                labelElement.textContent = `${id}`;

                toggleContainer.appendChild(checkbox);
                toggleContainer.appendChild(labelElement);
                document.getElementById('toggleControls').appendChild(toggleContainer);
            }

            function handleNode(node, parentType) {
                const leafColor = 0xffa500;
                const internalColor = 0x00ff00;
                if (node.type === 'AnvInternalNode') {
                    // Check if the internal node is a fatLeaf
                    const isFatProceduralLeaf = node.properties.node_type.nodeType === 0x3;
                    const isFatInstanceLeaf = node.properties.node_type.nodeType === 0x1;
                    node.properties.child_data.forEach((child, index) => {
                        if (child.blockIncr !== 1 && child.blockIncr !== 2) {
                            return;
                        }
                        const childIsProcedural = child.startPrim === 0x3 || isFatProceduralLeaf;
                        const childIsInstance = child.startPrim === 0x1 || isFatInstanceLeaf;
                        const color = (childIsProcedural || childIsInstance) ? leafColor : internalColor;
                        let label = node.id + "'s child box";
                        label += (childIsProcedural) ? " also a procedural leaf" : "";
                        label += (childIsInstance) ? " also a instance leaf" : "";
                        createBox(node.properties.actual_coords[index], color, label);
                    });
                } else {
                    switch (node.type) {
                        case 'AnvQuadLeafNode':
                            createTriangle(node.properties.v, leafColor, `Triangle. NodeID=${node.id}`);
                            break;
                        case 'AnvInstanceLeaf':
                            // Skip. Already drawn by parents
                            break;
                        case 'AnvAabbLeafNode':
                            // Skip. Already drawn by parents
                            break;
                    }
                }
            }

            // Draw AABB from header
            const headerAABB = treeData.header.aabb;
            createBox({
                x_lower: headerAABB.min_x,
                x_upper: headerAABB.max_x,
                y_lower: headerAABB.min_y,
                y_upper: headerAABB.max_y,
                z_lower: headerAABB.min_z,
                z_upper: headerAABB.max_z
            }, 0xff00ff, 'Root AABB');

            // Draw nodes
            treeData.nodes.forEach(node => {
                handleNode(node, node.properties.node_type);
            });

            camera.position.z = 5;

            function animate() {
                requestAnimationFrame(animate);
                controls.update();
                renderer.render(scene, camera);
            }
            animate();
        }

    </script>
</body>
</html>

